<?php

defined('SYSPATH') or die('Access error!');

/**
 *  请求分发
 */
class Request
{

    /**
     *  状态码
     * @var type 
     */
    public static $messages = array(
        100 => 'Continue',
        101 => 'Switching Protocols',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found', // 1.1
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        507 => 'Insufficient Storage',
        509 => 'Bandwidth Limit Exceeded'
    );

    /**
     * 获取值的方法： GET, POST, PUT, DELETE
     */
    public static $method = 'GET';

    /**
     * 协议: http, https, ftp, cli, etc
     */
    public static $protocol = 'http';

    /**
     * 来源连接
     */
    public static $referrer;

    /**
     * 客户端的 user agent
     */
    public static $user_agent = '';

    /**
     * 客户端的 IP address
     */
    public static $client_ip = '0.0.0.0';

    /**
     * 是否为ajax请求
     */
    public static $is_ajax = FALSE;

    /**
     * 用了存放request对象
     */
    public static $instance;

    /**
     * 当前的request对象
     */
    public static $current;

    /**
     *   用单例模式生成一个请求对象
     *   例如：
     *     $request = Request::instance();
     *
     * @param   string  
     * @return  Request
     */
    public static function instance(& $uri = TRUE)
    {
        if (!Request::$instance)
        {
            if (isset($_SERVER['REQUEST_METHOD']))
            {
                Request::$method = $_SERVER['REQUEST_METHOD'];
            }

            if (!empty($_SERVER['HTTPS']) AND filter_var($_SERVER['HTTPS'], FILTER_VALIDATE_BOOLEAN))
            {
                Request::$protocol = 'https';
            }


            if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
            {
                Request::$is_ajax = TRUE;
            }

            if (isset($_SERVER['HTTP_REFERER']))
            {
                Request::$referrer = $_SERVER['HTTP_REFERER'];
            }

            if (isset($_SERVER['HTTP_USER_AGENT']))
            {
                Request::$user_agent = $_SERVER['HTTP_USER_AGENT'];
            }

            if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
            {
                Request::$client_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
            }
            elseif (isset($_SERVER['HTTP_CLIENT_IP']))
            {
                Request::$client_ip = $_SERVER['HTTP_CLIENT_IP'];
            }
            elseif (isset($_SERVER['REMOTE_ADDR']))
            {
                Request::$client_ip = $_SERVER['REMOTE_ADDR'];
            }

            if (Request::$method !== 'GET' AND Request::$method !== 'POST')
            {
                parse_str(file_get_contents('php://input'), $_POST);
            }

            if ($uri === TRUE)
            {
                $uri = Request::detect_uri();
            }

            $uri = preg_replace('#//+#', '/', $uri);
            $uri = preg_replace('#\.[\s./]*/#', '', $uri);

            // 生成请求对象
            Request::$instance = Request::$current = new Request($uri);
            Request::$instance->headers['Content-Type'] = 'text/html; charset=' . Core::$charset;
        }

        return Request::$instance;
    }

    public static function detect_uri()
    {
        if (!empty($_SERVER['PATH_INFO']))
        {
            $uri = $_SERVER['PATH_INFO'];
        }
        else
        {

            if (isset($_SERVER['REQUEST_URI']))
            {
                $uri = $_SERVER['REQUEST_URI'];

                if ($request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))
                {
                    $uri = $request_uri;
                }
                $uri = rawurldecode($uri);
            }
            elseif (isset($_SERVER['PHP_SELF']))
            {
                $uri = $_SERVER['PHP_SELF'];
            }
            elseif (isset($_SERVER['REDIRECT_URL']))
            {
                $uri = $_SERVER['REDIRECT_URL'];
            }
            else
            {
                die('无法检测到使用PATH_INFO，REQUEST_URI，PHP_SELF或REDIRECT_URL的URI');
            }

            $base_url = parse_url(Core::$base_url, PHP_URL_PATH);

            if (strpos($uri, $base_url) === 0)
            {
                $uri = (string) substr($uri, strlen($base_url));
            }

            if (Core::$index_file AND strpos($uri, Core::$index_file) === 0)
            {
                $uri = (string) substr($uri, strlen(Core::$index_file));
            }
        }

        return $uri;
    }

    /**
     *  存储当前的request
     *     $request = Request::current();
     *
     * @return  Request
     */
    public function current()
    {
        return Request::$current;
    }

    /**
     * 创建一个request
     * 
     * @param type $uri
     * @return Request
     */
    public static function factory($uri)
    {
        return new Request($uri);
    }

    /**
     *     浏览器类型
     *     $browser = Request::user_agent('browser');
     *     返回浏览器和操作平台
     *     $info = Request::user_agent(array('browser', 'platform'));
     * @staticvar type $info
     * @param type $value
     * @return boolean
     */
    public static function user_agent($value)
    {
        if (is_array($value))
        {
            $agent = array();
            foreach ($value as $v)
            {
                $agent[$v] = Request::user_agent($v);
            }

            return $agent;
        }

        static $info;

        if (isset($info[$value]))
        {
            return $info[$value];
        }

        if ($value === 'browser' OR $value == 'version')
        {
            $browsers = Core::config('user_agents')->browser;

            foreach ($browsers as $search => $name)
            {
                if (stripos(Request::$user_agent, $search) !== FALSE)
                {
                    $info['browser'] = $name;

                    if (preg_match('#' . preg_quote($search) . '[^0-9.]*+([0-9.][0-9.a-z]*)#i', Request::$user_agent, $matches))
                    {
                        $info['version'] = $matches[1];
                    }
                    else
                    {
                        $info['version'] = FALSE;
                    }

                    return $info[$value];
                }
            }
        }
        else
        {
            $group = Core::config('user_agents')->$value;

            foreach ($group as $search => $name)
            {
                if (stripos(Request::$user_agent, $search) !== FALSE)
                {
                    return $info[$value] = $name;
                }
            }
        }

        return $info[$value] = FALSE;
    }

    /**
     * $types = Request::accept_type();
     */
    public static function accept_type($type = NULL)
    {
        static $accepts;

        if ($accepts === NULL)
        {
            $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT'], array('*/*' => 1.0));
        }

        if (isset($type))
        {
            return isset($accepts[$type]) ? $accepts[$type] : $accepts['*/*'];
        }

        return $accepts;
    }

    /**
     * $langs = Request::accept_lang();
     */
    public static function accept_lang($lang = NULL)
    {
        static $accepts;

        if ($accepts === NULL)
        {
            $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_LANGUAGE']);
        }

        if (isset($lang))
        {
            return isset($accepts[$lang]) ? $accepts[$lang] : FALSE;
        }

        return $accepts;
    }

    /**
     * $encodings = Request::accept_encoding();
     *
     * @param   string  
     * @return  float  
     * @return  array
     * @uses    Request::_parse_accept
     */
    public static function accept_encoding($type = NULL)
    {
        static $accepts;

        if ($accepts === NULL)
        {
            $accepts = Request::_parse_accept($_SERVER['HTTP_ACCEPT_ENCODING']);
        }

        if (isset($type))
        {
            return isset($accepts[$type]) ? $accepts[$type] : FALSE;
        }

        return $accepts;
    }

    /**
     *     $accept = Request::_parse_accept($header, $defaults);
     *
     * @param   string 
     * @param   array 
     * @return  array
     */
    protected static function _parse_accept(& $header, array $accepts = NULL)
    {
        if (!empty($header))
        {
            $types = explode(',', $header);

            foreach ($types as $type)
            {
                $parts = explode(';', $type);
                $type = trim(array_shift($parts));
                $quality = 1.0;

                foreach ($parts as $part)
                {
                    if (strpos($part, '=') === FALSE)
                        continue;
                    list ($key, $value) = explode('=', trim($part));

                    if ($key === 'q')
                    {
                        $quality = (float) trim($value);
                    }
                }

                $accepts[$type] = $quality;
            }
        }

        $accepts = (array) $accepts;
        arsort($accepts);
        return $accepts;
    }

    public $route;
    public $status = 200;
    public $response = '';
    public $headers = array();
    public $directory = '';
    public $controller;
    public $action;
    public $uri;
    protected $_params;
    public $module;

    /**
     *     $request = new Request($uri);
     *
     * @param   string  URI of the request
     * @return  void
     */
    public function __construct($uri)
    {
        $uri = trim($uri, '/');
        $routes = Route::all();

        foreach ($routes as $name => $route)
        {
            if ($params = $route->matches($uri))
            {
                $this->uri = $uri;

                $this->route = $route;

                if (isset($params['directory']))
                {
                    $this->directory = $params['directory'];
                }

                $this->controller = $params['controller'];

                if (isset($params['action']))
                {
                    $this->action = $params['action'];
                }
                else
                {
                    $this->action = Route::$default_action;
                }
                unset($params['controller'], $params['action'], $params['directory']);

                $this->_params = $params;

                return;
            }
        }

        $this->status = 404;
        
        $this->redirect('error/show');
    }

    /**
     *     echo $request;
     *
     * @return  string
     */
    public function __toString()
    {
        return (string) $this->response;
    }

    /**
     *     $request->uri($params);
     * @param   array  
     * @return  string
     * @uses    Route::uri
     */
    public function uri(array $params = NULL)
    {
        if (!isset($params['directory']))
        {
            $params['directory'] = $this->directory;
        }

        if (!isset($params['controller']))
        {
            $params['controller'] = $this->controller;
        }

        if (!isset($params['action']))
        {
            $params['action'] = $this->action;
        }

        $params += $this->_params;

        return $this->route->uri($params);
    }

    /**
     * echo URL::site($this->request->uri($params), $protocol);
     */
    public function url(array $params = NULL, $protocol = NULL)
    {
        return URL::site($this->uri($params), $protocol);
    }

    /**
     *     $id = $request->param('id');
     *
     * @param   string   
     * @param   mixed   
     * @return  mixed
     */
    public function param($key = NULL, $default = NULL)
    {
        if ($key === NULL)
        {
            return $this->_params;
        }

        return isset($this->_params[$key]) ? $this->_params[$key] : $default;
    }

    /**
     * 设置头部信息
     *     $request->send_headers();
     *
     * @return  $this
     * @uses    Request::$messages
     */
    public function send_headers()
    {
        if (!headers_sent())
        {
            if (isset($_SERVER['SERVER_PROTOCOL']))
            {
                $protocol = $_SERVER['SERVER_PROTOCOL'];
            }
            else
            {
                $protocol = 'HTTP/1.1';
            }

            header($protocol . ' ' . $this->status . ' ' . Request::$messages[$this->status]);

            foreach ($this->headers as $name => $value)
            {
                if (is_string($name))
                {
                    $value = "{$name}: {$value}";
                }

                header($value, TRUE);
            }

            foreach (Session::$instances as $session)
            {
                $session->write();
            }
        }
        return $this;
    }

    /**
     * 用于跳转的方法
     *     $request->redirect($url);
     * @param   string  
     * @param   integer
     * @return  void
     * @uses    URL::site
     * @uses    Request::send_headers
     */
    public function redirect($url = '', $code = 302)
    {
        if (strpos($url, '://') === FALSE)
        {
            $url = URL::site($url, TRUE);
        }
        $this->status = $code;
        $this->headers['Location'] = $url;
        $this->send_headers();
        exit;
    }

    /**
     * 下载文件
     * @param type $filename
     * @param type $download
     * @param array $options
     * @throws Core_Exception 
     */
    public function send_file($filename, $download = NULL, array $options = NULL)
    {
        if (!empty($options['mime_type']))
        {
            $mime = $options['mime_type'];
        }

        if ($filename === TRUE)
        {
            if (empty($download))
            {
                throw new Core_Exception('下载名称不存在！');
            }

            $options['delete'] = FALSE;

            if (!isset($mime))
            {
                $mime = File::get_mime(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
            }

            $file_data = (string) $this->response;

            $size = strlen($file_data);
            $file = tmpfile();
            fwrite($file, $file_data);
            unset($file_data);
        }
        else
        {
            $filename = realpath($filename);

            if (empty($download))
            {
                $download = pathinfo($filename, PATHINFO_BASENAME);
            }

            $size = filesize($filename);

            if (!isset($mime))
            {
                $mime = File::mime($filename);
            }

            $file = fopen($filename, 'rb');
        }

        if (!is_resource($file))
        {
            throw new Core_Exception('找不到文件：', array(
                ':file' => $download,
            ));
        }

        $disposition = empty($options['inline']) ? 'attachment' : 'inline';

        list($start, $end) = $this->_calculate_byte_range($size);

        if (!empty($options['resumable']))
        {
            if ($start > 0 OR $end < ($size - 1))
            {
                $this->status = 206;
            }

            $this->headers['Content-Range'] = 'bytes ' . $start . '-' . $end . '/' . $size;
            $this->headers['Accept-Ranges'] = 'bytes';
        }

        $this->headers['Content-Disposition'] = $disposition . '; filename="' . $download . '"';
        $this->headers['Content-Type'] = $mime;
        $this->headers['Content-Length'] = ($end - $start) + 1;

        if (Request::user_agent('browser') === 'Internet Explorer')
        {
            if (Request::$protocol === 'https')
            {
                $this->headers['Pragma'] = $this->headers['Cache-Control'] = 'public';
            }

            if (version_compare(Request::user_agent('version'), '8.0', '>='))
            {
                $this->headers['X-Content-Type-Options'] = 'nosniff';
            }
        }

        $this->send_headers();

        while (ob_get_level())
        {
            ob_end_flush();
        }

        ignore_user_abort(TRUE);

        if (!Core::$safe_mode)
        {
            set_time_limit(0);
        }

        $block = 1024 * 16;

        fseek($file, $start);

        while (!feof($file) AND ($pos = ftell($file)) <= $end)
        {
            if (connection_aborted())
                break;

            if ($pos + $block > $end)
            {
                $block = $end - $pos + 1;
            }

            echo fread($file, $block);

            flush();
        }

        fclose($file);

        if (!empty($options['delete']))
        {
            try
            {
                unlink($filename);
            } catch (Exception $e)
            {
                $error = Core::exception_text($e);

                if (is_object(Core::$log))
                {
                    Core::$log->add(Core::ERROR, $error);
                    Core::$log->write();
                }
            }
        }
        exit;
    }

    protected function _parse_byte_range()
    {
        if (!isset($_SERVER['HTTP_RANGE']))
        {
            return FALSE;
        }
        preg_match_all('/(-?[0-9]++(?:-(?![0-9]++))?)(?:-?([0-9]++))?/', $_SERVER['HTTP_RANGE'], $matches, PREG_SET_ORDER);
        return $matches[0];
    }

    protected function _calculate_byte_range($size)
    {
        $start = 0;
        $end = $size - 1;

        if ($range = $this->_parse_byte_range())
        {
            $start = $range[1];

            if ($start[0] === '-')
            {
                $start = $size - abs($start);
            }

            if (isset($range[2]))
            {
                $end = $range[2];
            }
        }

        $start = abs(intval($start));
        $end = min(abs(intval($end)), $size - 1);
        $start = ($end < $start) ? 0 : max($start, 0);

        return array($start, $end);
    }

    /**
     * 执行所要的请求
     * @return  $this
     */
    public function execute()
    {
        $prefix = 'controller_';
        if ($this->directory)
        {
            $prefix .= str_replace(array('\\', '/'), '_', trim($this->directory, '/')) . '_';
        }
        if (Core::$profiling)
        {
            $benchmark = '"' . $this->uri . '"';

            if ($this !== Request::$instance AND Request::$current)
            {
                $benchmark .= ' « "' . Request::$current->uri . '"';
            }

            $benchmark = Profiler::start('Requests', $benchmark);
        }
        $previous = Request::$current;
        Request::$current = $this;
        try
        {
            /**
             * 根据反射 实例化请求的类 并执行对应的方法
             */
            $class = new ReflectionClass($prefix . $this->controller);

            if ($class->isAbstract())
            {
                throw new Core_Exception('无法实例化： :controller',
                        array(':controller' => $prefix . $this->controller));
            }
            $controller = $class->newInstance($this);
            $class->getMethod('before')->invoke($controller);
            $action = empty($this->action) ? Route::$default_action : $this->action;
            $class->getMethod('action_' . $action)->invokeArgs($controller, $this->_params);
            $class->getMethod('after')->invoke($controller);
        } 
        catch (Exception $e)
        {
            
            Request::$current = $previous;

            if (isset($benchmark))
            {
                Profiler::delete($benchmark);
            }

            if ($e instanceof ReflectionException)
            {
                $this->status = 404;
                
                Request::$current->redirect('error/show');
            }
            else
            {
                $this->status = 500;
            }

            throw $e;
        }

        Request::$current = $previous;

        if (isset($benchmark))
        {
            Profiler::stop($benchmark);
        }

        return $this;
    }

    /**
     * $etag = $request->generate_etag();
     * @return string
     * @throws Core_Request_Exception
     */
    public function generate_etag()
    {
        if ($this->response === NULL)
        {
            throw new Core_Request_Exception('还要求与无响应 - 不能自动生成资源的ETag');
        }

        return '"' . sha1($this->response) . '"';
    }

    /**
     * 检查浏览器的缓存需要返回的响应。
     *     $request->check_cache($etag);
     * @param   string 
     * @return  $this
     * @throws  Core_Request_Exception
     * @uses    Request::generate_etag
     */
    public function check_cache($etag = null)
    {
        if (empty($etag))
        {
            $etag = $this->generate_etag();
        }
        $this->headers['ETag'] = $etag;

        $this->headers += array(
            'Cache-Control' => 'must-revalidate',
        );

        if (isset($_SERVER['HTTP_IF_NONE_MATCH']) AND $_SERVER['HTTP_IF_NONE_MATCH'] === $etag)
        {
            $this->status = 304;
            $this->send_headers();
            exit;
        }
        return $this;
    }

}
